import service
import util
import blue
import uthread
import entityCommon
import log
import random

class EntitySpawnService(service.Service):
    __guid__ = 'entities.EntitySpawnService'
    __exportedcalls__ = {}
    __notifyevents__ = ['ProcessEntitySceneUnloading', 'OnEntityDeleted']

    def Run(self, *args):
        self.generators = {}
        self.generatorThreads = {}
        self.lastSpawnIndexByGenerator = {}



    def OnLoadEntityScene(self, sceneID):
        uthread.new(self.LoadGenerators, sceneID).context = 'EntitySpawnService::OnLoadEntityScene'



    def OnEntitySceneLoaded(self, sceneID):
        return 



    def OnUnloadEntityScene(self, sceneID):
        return 



    def MapSceneIDToWorldSpaceID(self, sceneID):
        raise NotImplementedError('calling common version of this')



    def LoadGenerators(self, sceneID):
        worldSpaceTypeID = self.MapSceneIDToWorldSpaceID(sceneID)
        self.LogInfo('Loading scene generators for sceneID', sceneID, '==> worldSpaceTypeID', worldSpaceTypeID)
        self.generators[sceneID] = []
        for generator in self.GetGenerators(sceneID):
            if ((boot.role == 'server') and generator.clientOnly):
                continue
            generatorSpawns = self.GetGeneratorSpawns(generator.generatorID)
            newGenerator = util.KeyVal(generatorID=generator.generatorID, sceneID=sceneID, worldSpaceTypeID=generator.worldSpaceTypeID, spawns=generatorSpawns, lastSpawnTime=None, maxSpawns=generator.maxSpawns, spawnPattern=generator.spawnPattern, spawnInterval=(generator.spawnInterval * SEC), spawnPoint=(generator.spawnPointX,
             generator.spawnPointY,
             generator.spawnPointZ), spawnRotation=(generator.spawnRotationY,
             generator.spawnRotationX,
             generator.spawnRotationZ), spawnRadius=generator.spawnRadius, randomRotation=generator.randomRotation, currentSpawns=[], active=True)
            self.generators[sceneID].append(newGenerator)

        self.generatorThreads[sceneID] = uthread.new(self._SpawnLoop, sceneID)
        self.generatorThreads[sceneID].context = 'EntitySpawnService::LoadGenerators'



    def GetGeneratorSpawns(self, generatorID):
        generatorSpawns = []
        spawnRows = cfg.entitySpawnByGenerator.get(generatorID, [])
        for spawn in spawnRows:
            spawnOverrides = self._GetSpawnOverrides(spawn.spawnID)
            newSpawn = util.KeyVal(spawnID=spawn.spawnID, typeID=spawn.typeID, overrides=spawnOverrides, generatorID=generatorID)
            generatorSpawns.append(newSpawn)

        return generatorSpawns



    def _GetSpawnOverrides(self, spawnID):
        spawnOverrides = {}
        overrideRows = cfg.entitySpawnInitsBySpawn.get(spawnID, [])
        for overrideRow in overrideRows:
            if (overrideRow.componentID not in spawnOverrides):
                spawnOverrides[overrideRow.componentID] = {}
            spawnOverrides[overrideRow.componentID][overrideRow.initialValueName] = entityCommon.GetIngredientInitialValue(overrideRow)

        return spawnOverrides



    def _SpawnLoop(self, sceneID):
        while (self.state == service.SERVICE_RUNNING):
            if ((sceneID not in self.generators) or (len(self.generators[sceneID]) < 1)):
                break
            (generator, nextSpawnTime,) = self._GetNextSpawn(self.generators[sceneID])
            while ((nextSpawnTime is not None) and (nextSpawnTime <= blue.os.GetTime())):
                self._Spawn(generator)
                (generator, nextSpawnTime,) = self._GetNextSpawn(self.generators[sceneID])

            if (nextSpawnTime is None):
                break
            generator = None
            sleepyTime = ((nextSpawnTime - blue.os.GetTime()) / const.MSEC)
            self.LogInfo('_SpawnLoop :: Spawning for scene', sceneID, 'sleeping for', sleepyTime, 'msec')
            blue.pyos.synchro.Sleep(sleepyTime)




    def _GetNextSpawn(self, generatorList):
        if (len(generatorList) < 1):
            return (None, None)
        else:
            earliestGenerator = earliestSpawnTime = None
            for generator in generatorList:
                if not generator.active:
                    continue
                if (len(generator.currentSpawns) >= generator.maxSpawns):
                    continue
                if (earliestGenerator is None):
                    earliestGenerator = generator
                    earliestSpawnTime = ((generator.lastSpawnTime + generator.spawnInterval) if (generator.lastSpawnTime is not None) else blue.os.GetTime())
                elif (generator.lastSpawnTime is None):
                    earliestGenerator = generator
                    earliestSpawnTime = blue.os.GetTime()
                    break
                else:
                    if ((generator.lastSpawnTime + generator.spawnInterval) < earliestSpawnTime):
                        earliestGenerator = generator
                        earliestSpawnTime = (generator.lastSpawnTime + generator.spawnInterval)

            return (earliestGenerator, earliestSpawnTime)



    def _Spawn(self, generator):
        spawnedEntityID = self.AppSpawn(generator)
        if (spawnedEntityID is not None):
            generator.currentSpawns.append(spawnedEntityID)
        generator.lastSpawnTime = blue.os.GetTime()



    def AppSpawn(self, generator):
        raise NotImplementedError('Game client must define its own AppSpawn!')



    def ProcessEntitySceneUnloading(self, sceneID):
        self.LogInfo('EntitySpawnService::OnEntitySceneUnloading', sceneID)
        generators = self.generators.get(sceneID, [])
        for generator in generators:
            generator.active = False

        thread = self.generatorThreads.get(sceneID, None)
        if ((thread is not None) and thread.alive):
            thread.kill()



    def OnEntityDeleted(self, entityID, sceneID):
        generators = self.generators.get(sceneID, [])
        entityGenerator = None
        for generator in generators:
            if (entityID in generator.currentSpawns):
                entityGenerator = generator
                break

        if (entityGenerator is not None):
            entityGenerator.currentSpawns.remove(entityID)
            if (len(entityGenerator.currentSpawns) < entityGenerator.maxSpawns):
                if ((sceneID not in self.generatorThreads) or (not self.generatorThreads[sceneID].alive)):
                    self.LogInfo('OnEntityDeleted found generator', generator.generatorID, 'without live spawning thread - restarting spawn thread')
                    self.generatorThreads[sceneID] = uthread.new(self._SpawnLoop, sceneID)
                    self.generatorThreads[sceneID].context = 'EntitySpawnService::OnEntityDeleted'



    def OverridePosition(self, overrides, position, rotation):
        positionComponentID = const.zentity.POSITION_COMPONENT_ID
        if (positionComponentID not in overrides):
            overrides[positionComponentID] = {}
        overrides[positionComponentID]['position'] = position
        overrides[positionComponentID]['rotation'] = rotation
        return overrides



    def FindRandomPointAtFloor(self, position, radius, gameworld):
        posAtFloor = None
        tries = 0
        while not posAtFloor:
            newX = (position[0] + random.uniform(-radius, radius))
            newY = position[1]
            newZ = (position[2] + random.uniform(-radius, radius))
            posAtFloor = gameworld.GetHeightAtPoint((newX,
             newY,
             newZ), 2.0, 2.0)
            blue.pyos.BeNice()
            tries += 1
            if (tries > 50):
                break

        if posAtFloor:
            position = posAtFloor[0]
        else:
            log.LogError('Generator did not find a random spot on the floor, reveting to orgina position')
        return position




